package com.inno.subject.domain.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import com.alibaba.fastjson.JSON;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.inno.subject.common.constant.RedisKeyPrefix;
import com.inno.subject.common.context.LoginUserContext;
import com.inno.subject.common.entity.PageResult;
import com.inno.subject.common.enums.IsDeletedFlagEnum;
import com.inno.subject.common.enums.SubjectLevelEnum;
import com.inno.subject.common.enums.YesOrNoEnum;
import com.inno.subject.common.error.BusinessException;
import com.inno.subject.common.utils.DateUtils;
import com.inno.subject.common.utils.RedisKeyBuilder;
import com.inno.subject.domain.convert.SubjectInfoConverter;
import com.inno.subject.domain.convert.SubjectRecordsConverter;
import com.inno.subject.domain.entity.CategorySubjectCountBO;
import com.inno.subject.domain.entity.SubjectAnswerBO;
import com.inno.subject.domain.entity.SubjectInfoBO;
import com.inno.subject.domain.entity.SubjectRecordsBO;
import com.inno.subject.domain.redis.RedisUtil;
import com.inno.subject.domain.service.SubjectInfoDomainService;
import com.inno.subject.domain.strategy.subject.SubjectHandlerFactory;
import com.inno.subject.domain.utils.ApplicationContextUtil;
import com.inno.subject.domain.utils.ChatClientUtil;
import com.inno.subject.infra.basic.entity.*;
import com.inno.subject.infra.basic.service.*;
import com.inno.subject.infra.entity.CategorySubjectCountEntity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.util.Pair;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author sean
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SubjectInfoDomainServiceImpl implements SubjectInfoDomainService {
    private final PlannerSubjectInfoService plannerSubjectInfoService;
    private final ThreadPoolTaskExecutor subjectExecutor;
    private final PlannerSubjectCategoryService plannerSubjectCategoryService;
    private final PlannerSubjectTypeService plannerSubjectTypeService;
    private final PlannerSubjectCollectService plannerSubjectCollectService;
    private final PlannerSubjectAnswerService plannerSubjectAnswerService;
    private final PlannerSubjectRecordsService plannerSubjectRecordsService;
    private final RedisUtil redisUtil;
    private final SubjectHandlerFactory subjectHandlerFactory;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(SubjectInfoBO subjectInfoBO) {
        PlannerSubjectInfo entity = SubjectInfoConverter.INSTANCE.convertBOToEntity(subjectInfoBO);
        entity.setCreateBy(subjectInfoBO.getCurrentUser());
        entity.setCreateTime(DateUtils.getNowDate());
        entity.setId(IdUtil.getSnowflakeNextId());
        entity.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode().toString());

        subjectInfoBO.setId(entity.getId());
        subjectHandlerFactory.getSubjectHandler(subjectInfoBO.getSubjectType()).handle(subjectInfoBO,entity);

    }

    /**
     * 题目列表
     */
    @Override
    public PageResult<SubjectInfoBO> queryPage(SubjectInfoBO subjectInfoBO) {
        // 仅缓存前10页无条件查询的数据  避免占用内存过多
        boolean needFromCache = StringUtils.isBlank(subjectInfoBO.getSubjectName())
                && StringUtils.isBlank(subjectInfoBO.getSubjectLevel())
                && Objects.isNull(subjectInfoBO.getSubjectType())
                && Objects.isNull(subjectInfoBO.getCategoryId())
                && subjectInfoBO.getPage() <= 10
                ;

        // 需要走缓存
        if (needFromCache) {
            String cacheKey = RedisKeyBuilder.builderSubjectPageKey(subjectInfoBO.getPage(), subjectInfoBO.getPageSize());
            Map<Object, Object> cache = redisUtil.hmget(cacheKey);
            if (CollectionUtil.isEmpty(cache)) {
                PageResult<SubjectInfoBO> pageResult = ApplicationContextUtil.getBean(this.getClass()).queryPageFromDatabase(subjectInfoBO);
                Map<String, Object> saveCache = new HashMap<>();
                saveCache.put("total", pageResult.getTotal());
                saveCache.put("list", JSON.toJSONString(pageResult.getList()));
                redisUtil.hmset(cacheKey, saveCache);
                return pageResult;
            } else {
                long total = Long.parseLong(cache.get("total").toString());
                List<SubjectInfoBO> list = JSON.parseArray(cache.get("list").toString(), SubjectInfoBO.class);
                return PageResult.of(list, total);
            }
        }

        return ApplicationContextUtil.getBean(this.getClass()).queryPageFromDatabase(subjectInfoBO);

    }

    @NotNull
    @Transactional(readOnly = true)
    public PageResult<SubjectInfoBO> queryPageFromDatabase(SubjectInfoBO subjectInfoBO) {
        PlannerSubjectInfo entity = SubjectInfoConverter.INSTANCE.convertBOToEntity(subjectInfoBO);
        entity.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode().toString());
//        PageHelper.startPage(subjectInfoBO.getPage(), subjectInfoBO.getPageSize());
        // 计算页码
        int pageNum = subjectInfoBO.getPage();
        int pageSize = subjectInfoBO.getPageSize();
        int offset = (pageNum - 1) * pageSize;
        Long pageCount = this.plannerSubjectInfoService.queryPageCount(entity);
        List<PlannerSubjectInfo> entityResult = this.plannerSubjectInfoService.queryPage(entity, offset, pageSize);
        PageResult<SubjectInfoBO> boResult = new PageResult<>();
        if (CollectionUtil.isEmpty(entityResult)) {
            boResult.setTotal(0L);
            boResult.setList(Collections.emptyList());
            return boResult;
        }
        boResult.setTotal(pageCount);
        List<SubjectInfoBO> boList = new ArrayList<>();


        Pair<Map<Long, String>, Map<Long, String>> typeAndCategory = asyncQueryTypeAndCategory(entityResult);
        Map<Long, String> typeMap = typeAndCategory.getFirst();
        Map<Long, String> categoryMap = typeAndCategory.getSecond();
        Map<Long, Long> collectCountMap = this.getCollectCountMap(entityResult);

        entityResult.forEach(item -> {
            SubjectInfoBO bo = SubjectInfoConverter.INSTANCE.convertEntityToBO(item);
            bo.setCategoryName(categoryMap.get(item.getCategoryId()));
            bo.setSubjectTypeName(typeMap.get(item.getSubjectType()));
            bo.setSubjectLevelName(SubjectLevelEnum.getDescByCode(bo.getSubjectLevel()));
            bo.setCollectCount(collectCountMap.getOrDefault(item.getId(), 0L));
            boList.add(bo);
        });

        Map<Long, List<SubjectInfoBO>> subjectIdToBoListMap = boList.stream()
                .collect(Collectors.groupingBy(SubjectInfoBO::getId));

        List<PlannerSubjectCollect> collectList = this.plannerSubjectCollectService.queryBySubjectIdsAndUserId(
                subjectIdToBoListMap.keySet(), subjectInfoBO.getCurrentUser());

        Map<Long, Boolean> isCollectMap = new HashMap<>();
        collectList.forEach(collect -> {
            isCollectMap.put(collect.getSubjectId(), true);
        });

        subjectIdToBoListMap.forEach((subjectId, boSubList) -> {
            boSubList.forEach(bo -> {
                bo.setIsCollect(isCollectMap.getOrDefault(subjectId, false) ? YesOrNoEnum.YES.getCode() : YesOrNoEnum.NO.getCode());
            });
        });
        boResult.setList(boList);
        return boResult;
    }

    /**
     * 获取题目收藏数
     */
    private Map<Long, Long> getCollectCountMap(List<PlannerSubjectInfo> entityResult) {
        List<Long> subjectIds = entityResult.stream().map(PlannerSubjectInfo::getId).toList();
        List<CompletableFuture<Long>> futures = new ArrayList<>(subjectIds.size());
        for (int i = 0; i < subjectIds.size(); i++) {
            final int index = i;
            futures.add( CompletableFuture.supplyAsync(() -> plannerSubjectCollectService.queryCollectCountBySubjectId(subjectIds.get(index)), subjectExecutor));
        }

        // 等待所有异步任务完成，并将结果合并为一个Map
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
                .thenApply(v -> IntStream.range(0, subjectIds.size())
                        .boxed()
                        .collect(Collectors.toMap(subjectIds::get, i -> futures.get(i).join())))
                .join();
    }


    /**
     * 题目详情
     */
    @Override
    public SubjectInfoBO queryById(SubjectInfoBO subjectInfoBO) {
        String key = RedisKeyBuilder.builderSubjectDetailKey(subjectInfoBO.getId());
        Object cache = redisUtil.get(key);
        if (Objects.nonNull(cache)) {
            return JSON.parseObject(cache.toString(), SubjectInfoBO.class);
        } else {
            SubjectInfoBO fromDatabase = ApplicationContextUtil.getBean(this.getClass()).queryDetailFromDatabase(subjectInfoBO);
            redisUtil.set(key, JSON.toJSONString(fromDatabase), 60 * 60);
            return fromDatabase;
        }

    }


    @Transactional(readOnly = true)
    public SubjectInfoBO queryDetailFromDatabase(SubjectInfoBO subjectInfoBO) {
        PlannerSubjectInfo entityResult = this.plannerSubjectInfoService.queryById(subjectInfoBO.getId());
        if (Objects.isNull(entityResult)) {
            throw new BusinessException("题目已经删除或下架,请查看其他题目");
        }
        SubjectInfoBO boResult = SubjectInfoConverter.INSTANCE.convertEntityToBO(entityResult);
        Long categoryId = boResult.getCategoryId();
        String subjectLevel = boResult.getSubjectLevel();
        Long subjectType = boResult.getSubjectType();
        Date createTime = boResult.getCreateTime();
        Long nextSubjectId = this.plannerSubjectInfoService.queryCursor(categoryId, subjectLevel, subjectType, 1, createTime);
        boResult.setNextSubjectId(nextSubjectId);
        Long lastSubjectId = this.plannerSubjectInfoService.queryCursor(categoryId, subjectLevel, subjectType, 0, createTime);
        boResult.setLastSubjectId(lastSubjectId);

        Long collectCount = plannerSubjectCollectService.queryCollectCountBySubjectId(boResult.getId());
        PlannerSubjectType plannerSubjectType = this.plannerSubjectTypeService.queryById(boResult.getSubjectType());
        if ("选择题".equals(plannerSubjectType.getTypeName())) {
            PlannerSubjectAnswer subjectAnswer = plannerSubjectAnswerService.queryBySubjectId(boResult.getId());
            List<SubjectAnswerBO> subjectAnswerBOList = JSON.parseArray(subjectAnswer.getAnswerContent(), SubjectAnswerBO.class);
            boResult.setSubjectOptions(subjectAnswerBOList);
            List<String> answerOptions = JSON.parseArray(subjectAnswer.getAnswerOptions(), String.class);
            List<SubjectAnswerBO> subjectOptions = new ArrayList<>();
            for (String answerOption : answerOptions) {
                SubjectAnswerBO option = new SubjectAnswerBO();
                option.setOption(answerOption);
                subjectOptions.add(option);
            }
            boResult.setSubjectOptionAnswer(subjectOptions);

        }

        PlannerSubjectCategory plannerSubjectCategory = this.plannerSubjectCategoryService.queryById(boResult.getCategoryId());
        boResult.setSubjectTypeName(plannerSubjectType.getTypeName());
        boResult.setCategoryName(plannerSubjectCategory.getCategoryName());
        boResult.setSubjectLevelName(SubjectLevelEnum.getDescByCode(boResult.getSubjectLevel()));
        boResult.setCollectCount(collectCount);
        PlannerSubjectCollect collect = new PlannerSubjectCollect();
        collect.setUserId(subjectInfoBO.getCurrentUser());
        collect.setSubjectId(boResult.getId());
        PlannerSubjectCollect plannerSubjectCollect = this.plannerSubjectCollectService.queryBySubjectIdAndUserId(collect);
        boResult.setIsCollect(plannerSubjectCollect == null ? YesOrNoEnum.NO.getCode() : YesOrNoEnum.YES.getCode());
        return boResult;
    }

    /**
     * 修改题目
     */
    @Override
    public void update(SubjectInfoBO subjectInfoBO) {
        PlannerSubjectInfo entity = SubjectInfoConverter.INSTANCE.convertBOToEntity(subjectInfoBO);
        entity.setUpdateBy(LoginUserContext.getUserId());
        entity.setUpdateTime(DateUtils.getNowDate());
        this.plannerSubjectInfoService.update(entity);
    }

    @Override
    public void delete(List<Long> ids) {
        this.plannerSubjectInfoService.batchDeleteByIdList(ids);
    }

    @Override
    public PageResult<SubjectInfoBO> queryErrorSubject(SubjectInfoBO subjectInfoBO) {
        SubjectRecordsBO subjectRecordsBO = new SubjectRecordsBO();
        subjectRecordsBO.setIsRight(YesOrNoEnum.NO.getCode());
        subjectRecordsBO.setUserId(subjectInfoBO.getCurrentUser());
        PlannerSubjectRecords records = SubjectRecordsConverter.INSTANCE.convertBOToEntity(subjectRecordsBO);
        PageHelper.startPage(subjectInfoBO.getPage(), subjectInfoBO.getPageSize());
        Page<PlannerSubjectRecords> recordsPage = this.plannerSubjectRecordsService.queryErrorSubjectId(records);
        List<PlannerSubjectRecords> recordsList = recordsPage.getResult();
        if (CollectionUtil.isEmpty(recordsList)) {
            return PageResult.of(Collections.emptyList(), 0L);
        }
        List<Long> subjectIdList = recordsList.stream().map(PlannerSubjectRecords::getSubjectId).toList();
        List<PlannerSubjectInfo> subjectInfoList = querySubjectList(subjectIdList);

        Pair<Map<Long, String>, Map<Long, String>> typeAndCategory = asyncQueryTypeAndCategory(subjectInfoList);

        List<SubjectInfoBO> subjectInfoBOList = SubjectInfoConverter.INSTANCE.convertEntityListToBOList(subjectInfoList);

        Map<Long, String> typeMap = typeAndCategory.getFirst();
        Map<Long, String> categoryMap = typeAndCategory.getSecond();
        subjectInfoBOList.forEach(bo -> {
            bo.setCategoryName(categoryMap.get(bo.getCategoryId()));
            bo.setSubjectTypeName(typeMap.get(bo.getSubjectType()));
            bo.setSubjectLevelName(SubjectLevelEnum.getDescByCode(bo.getSubjectLevel()));
            recordsList.forEach(record -> {
                if (record.getSubjectId().equals(bo.getId())) {
                    bo.setRecordTime(record.getCreateTime());
                    bo.setRecordAnswer(record.getRecordAnswer());
                    bo.setRightAnswer(record.getRightAnswer());
                }
            });

        });

        return PageResult.of(subjectInfoBOList, recordsPage.getTotal());
    }

    private List<PlannerSubjectInfo> querySubjectList(List<Long> subjectIdList) {
        return this.plannerSubjectInfoService.queryByIdList(subjectIdList);
    }

    @Override
    public Long queryCount() {
        PlannerSubjectInfo entity = new PlannerSubjectInfo();
        entity.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode().toString());
        return this.plannerSubjectInfoService.queryPageCount(entity);
    }

    @Override
    public List<CategorySubjectCountBO> categorySubjectCount() {
        List<CategorySubjectCountEntity> categorySubjectCountEntityList = this.plannerSubjectInfoService.queryCategorySubjectCount();
        List<CategorySubjectCountBO> result = new ArrayList<>();
        for (CategorySubjectCountEntity categorySubjectCountEntity : categorySubjectCountEntityList) {
            CategorySubjectCountBO bo = new CategorySubjectCountBO();
            BeanUtil.copyProperties(categorySubjectCountEntity, bo);
            result.add(bo);
        }
        return result;
    }

    /**
     * 每日一练
     */
    @Override
    public List<SubjectInfoBO> daily(SubjectInfoBO subjectInfoBO) {
        String key = RedisKeyBuilder.builderSubjectDaily();
        Object cache = redisUtil.get(key);
        if (ObjUtil.isNotNull(cache)) {
            try {
                return JSON.parseArray(cache.toString(), SubjectInfoBO.class);
            } catch (Exception e) {
                log.error("redis缓存解析异常", e);
                return this.queryDailyFromDatabase(subjectInfoBO);
            }
        } else {
            List<SubjectInfoBO> subjectInfoBOList = this.queryDailyFromDatabase(subjectInfoBO);
            redisUtil.set(key, JSON.toJSONString(subjectInfoBOList), 60 * 60);
            return subjectInfoBOList;
        }

    }

    private List<SubjectInfoBO> queryDailyFromDatabase(SubjectInfoBO subjectInfoBO) {
        List<Long> subjectIdList = this.plannerSubjectRecordsService.queryCountGroupSubjectId(subjectInfoBO.getCurrentUser());
        if (CollectionUtil.isEmpty(subjectIdList)) {
            SubjectInfoBO queryParam = new SubjectInfoBO();
            queryParam.setPage(1);
            queryParam.setPageSize(5);
            PageResult<SubjectInfoBO> pageResult = this.queryPage(queryParam);
            return pageResult.getList();
        }
        List<PlannerSubjectInfo> subjectInfoList = querySubjectList(subjectIdList);
        List<SubjectInfoBO> result = SubjectInfoConverter.INSTANCE.convertEntityListToBOList(subjectInfoList);
        Pair<Map<Long, String>, Map<Long, String>> typeAndCategory = asyncQueryTypeAndCategory(subjectInfoList);
        Map<Long, String> categoryMap = typeAndCategory.getSecond();
        Map<Long, String> typeMap = typeAndCategory.getFirst();
        result.forEach(r -> {
            r.setCategoryName(categoryMap.get(r.getCategoryId()));
            r.setSubjectTypeName(typeMap.get(r.getSubjectType()));
            r.setSubjectLevelName(SubjectLevelEnum.getDescByCode(r.getSubjectLevel()));
        });
        return result;
    }


    private Pair<Map<Long,String>,Map<Long,String>> asyncQueryTypeAndCategory(List<PlannerSubjectInfo> subjectInfoList){
        CompletableFuture<List<PlannerSubjectCategory>> categoryFuture = CompletableFuture.supplyAsync(() -> {
            List<Long> categoryIds = subjectInfoList.stream().map(PlannerSubjectInfo::getCategoryId).toList();
            return this.plannerSubjectCategoryService.queryByIdList(categoryIds);
        }, subjectExecutor);

        CompletableFuture<List<PlannerSubjectType>> typeFuture = CompletableFuture.supplyAsync(() -> {
            List<Long> typeIdList = subjectInfoList.stream().map(PlannerSubjectInfo::getSubjectType).toList();
            return this.plannerSubjectTypeService.queryByIdList(typeIdList);
        }, subjectExecutor);

        Map<Long, String> categoryMap = categoryFuture.join()
                .stream()
                .collect(Collectors.toMap(PlannerSubjectCategory::getId, PlannerSubjectCategory::getCategoryName));
        Map<Long, String> typeMap = typeFuture.join()
                .stream()
                .collect(Collectors.toMap(PlannerSubjectType::getId, PlannerSubjectType::getTypeName));
        return Pair.of(typeMap,categoryMap);
    }
    
    
}
